/*
 * Copyright (c) 2011 Imre Fazekas.
 * All rights reserved.
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 * Redistributions of source code must retain the above copyright notice, this
 * list of conditions and the following disclaimer.
 *
 * Redistributions in binary form must reproduce the above copyright notice,
 * this list of conditions and the following disclaimer in the documentation
 * and/or other materials provided with the distribution.
 * Neither the name of the Brillien nor the names of its
 * terms and concepts may be used to endorse or promote products derived from this
 * software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 */

package com.vii.streamline.structures.collections.tree;

import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;

/**
 * Simple tree data structure implementation
 */
public class TreeNode<V> implements Iterable<TreeNode>{

    /**
     * Metadata of the node
     */
    protected V                 metadata;

    /**
     * Data of the node
     */
    protected V                 data;

    /**
     * Reference to the parent node
     */
    protected TreeNode<V>       parent;

    /**
     * Collection of the child nodes
     */
    protected List<TreeNode>    children;

    /**
     * Collection of nodes being associated with
     */
    protected List<TreeNode>    connections;

    {
        children    = new LinkedList<TreeNode>();
        connections = new LinkedList<TreeNode>();
    }

    /**
     * Creates a new TreeNode instance from the given data
     * @param data data of the actual node
     */
    public TreeNode(V data) {
        this( data, (TreeNode<V>)null );
    }

    /**
     * Creates a new TreeNode instance from the given data and parent node
     * @param data data of the actual node
     * @param parent parent node
     */
    protected TreeNode(V data, TreeNode<V> parent) {
        this.data = data;
        this.parent = parent;
    }

    /**
     * Creates a new TreeNode instance from the given data
     * @param metadata metadata of the actual node
     * @param data data of the actual node
     */
    public TreeNode(V metadata, V data) {
        this( metadata, data, null );
    }

    /**
     * Creates a new TreeNode instance from the given data and parent node
     * @param metadata metadata of the actual node
     * @param data data of the actual node
     * @param parent parent node
     */
    protected TreeNode(V metadata, V data, TreeNode<V> parent) {
        this.metadata = metadata;
        this.data = data;
        this.parent = parent;
    }

    /**
     * Constructor-like methode to be polimorfised
     */
    protected TreeNode newTreeNode(V data){
        return new TreeNode( data );
    }

    /**
     * Constructor-like methode to be polimorfised
     */
    protected TreeNode newTreeNode(V data, TreeNode<V> parent){
        return new TreeNode( data, parent );
    }

    /**
     * Constructor-like methode to be polimorfised
     */
    protected TreeNode newTreeNode(V metadata, V data){
        return new TreeNode( metadata, data );
    }

    /**
     * Constructor-like methode to be polimorfised
     */
    protected TreeNode newTreeNode(V metadata, V data, TreeNode<V> parent){
        return new TreeNode( metadata, data, parent );
    }

    /**
     * Getter method for the parent node
     */
    public TreeNode<V> getParent() {
        return parent;
    }

    /**
     * Setter method for the parent node
     */
    public void setParent(TreeNode<V> parent) {
        this.parent = parent;
        parent.addChild( this );
    }

    /**
     * Getter method for the child nodes
     */
    public List<TreeNode> getChildren() {
        return children;
    }

    /**
     * Getter method for the connection nodes
     */
    public List<TreeNode> getConnections() {
        return connections;
    }

    /**
     * Adds a new child to the actual node
     * @param node child node to be added
     * @return reference to the new child node
     */
    public TreeNode addChild(TreeNode<V> node){
        if( hasChild( node.data ) ) return getChild( node.data );
        children.add( node );
        return node;
    }

    /**
     * Adds a new child to the actual node
     * @param data data of the child node to be added
     * @return reference to the new child node
     */
    public TreeNode addChild(V data){
        TreeNode node = newTreeNode(data, this);
        children.add( node ); node.setParent( this );
        return node;
    }

    /**
     * Removes a child node identified by the data content
     * @param data data of the child to be removed
     */
    public void removeChild(V data){
        children.remove( newTreeNode(data) );
    }

    /**
     * Removes child nodes
     */
    public void clearChildren( ){
        children.clear( );
    }

    /**
     * Adds a connection node to the actual node
     * @param data data of the connection node to be created
     * @return reference to the new connection node
     */
    public TreeNode addConnection(V data){
        TreeNode node = newTreeNode(data, this);
        connections.add( node );
        return node;
    }

    /**
     * Removes a connection node identified by the data content
     * @param data data of the connection to be removed
     */
    public void removeConnection(V data){
        connections.remove( newTreeNode(data) );
    }

    /**
     * Removes connection nodes
     */
    public void clearConnections( ){
        connections.clear( );
    }

    /**
     * Getter method for the metadata of the node
     */
    public V getMetadata() {
        return metadata;
    }

    /**
     * Getter method for the data of the node
     */
    public V getData() {
        return data;
    }

    /**
     * Getter method to access the child at the given index
     * @param index index od the child
     * @return reference of child node
     */
    public TreeNode<V> getChild(int index){
        return children.get( index );    
    }

    /**
     * Getter method to access the child by data (recursively)
     * @param data data of the searched child node
     * @return reference of the child node
     */
    public TreeNode<V> getChild(V data){ TreeNode<V> found;
        if( !data.equals( data ) ){
            for( TreeNode<V> child : children )
                if( (found = child.getChild( data )) != null )
                    return found;
        }
        return null;
    }

    /**
     * Decides whther a child node with the give data exists or not
     * @param data data of the searched child node
     */
    public boolean hasChild(V data){
        return getChild( data ) != null;
    }

    /**
     * Decides whether a direct child node with the given data exists
     */
    public boolean isDirectChild(V data){
        for( TreeNode<V> child : children )
            if( child.data.equals( data ) )
                return true;
        return false;
    }

    /**
     * Special method to search the existence of the given data pair in parent-child relationship.
     * @param p data of the parent
     * @param c data of the child
     * @return result of the searching
     */
    public boolean isDirectChild( V p, V c ){
        if( data.equals(p) && isDirectChild( c ) )
            return true;
        for( TreeNode<V> child : children )
            if( child.isDirectChild( p, c ) )
                return true;
        return false;
    }

    /**
     * Computes the level number of this node. Root node is 1.
     */
    public int getLevel(){
        return parent == null? 1 : parent.getLevel() + 1;
    }

    /**
     * Equality is considered by the equality of the data content   
     */
    @Override        
    public boolean equals(Object o) {
        if (this == o) return true;
        if (!(o instanceof TreeNode)) return false;

        TreeNode treeNode = (TreeNode) o;

        if (data != null ? !data.equals(treeNode.data) : treeNode.data != null) return false;

        return true;
    }

    /**
     * Tells whether the actual node is a leaf
     */
    public boolean isLeaf(){
        return children.size() == 0;
    }

    /**
     * Iterator method to access the children nodes
     */
    public Iterator<TreeNode> iterator() {
        return children.iterator();  
    }


}
